package kode.boot.logging.aop;

import kode.boot.logging.LoggingInfo;
import kode.boot.logging.LoggingListener;
import kode.boot.logging.LoggingResolver;
import lombok.Getter;
import lombok.Setter;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.Ordered;
import org.springframework.util.ClassUtils;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

@Getter
@Setter
public class LoggingAdvice implements MethodInterceptor {

	private List<LoggingListener> listeners = new ArrayList<>();
	private List<LoggingResolver> resolvers = new ArrayList<>();
	private ApplicationEventPublisher eventPublisher;

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		Object result;
		LoggingInfo info = createLogger(invocation);
		try {
			eventPublisher.publishEvent(info);
			listeners.forEach(listener -> listener.onLoggingBefore(info));
			result = invocation.proceed();
			info.setResult(result);
		} catch (Throwable e) {
			info.setException(e);
			throw e;
		} finally {
			info.setResponseTime(System.currentTimeMillis());
			eventPublisher.publishEvent(info);
			listeners.forEach(listener -> listener.onLoggingAfter(info));
		}
		return result;
	}

	private LoggingInfo createLogger(MethodInvocation invocation) {
		var method = invocation.getMethod();

		var info = resolvers.stream()
				.filter(parser -> parser.support(ClassUtils.getUserClass(invocation.getThis()), method))
				.min(Comparator.comparing(Ordered::getOrder, Comparator.reverseOrder()))
				.map(parser -> parser.resolve(invocation))
				.orElse(null);

		if (info == null) {
			info = new LoggingInfo();
		}

		return info;
	}
}
